/*
  ts就是一个强类型版本的js
  ts可以做运行时报错
  语法比较严格，不容易出错
  多人协作的大项目更加友好

  1. 安装ts
    cnpm i typescript -g

  2. 编译ts
    tsc ./src/index.ts --outFile ./dist/index.js

  3. 初始化ts的配置文件
    会出现一个项目的ts配置文件
    tsc --init

  4. 开启实时监听
    tsc --watch
*/

// 定义字符串类型
// 定义好了类型以后就不能改成其他类型了
let str1: string = "hello";
console.log(str1);
str1 = "5";
// 也可以先定义但是不赋值
let str2: string;
str2 = "world";
// ts的自动类型推断功能
let str3 = "123";
str3 = "456";

// 定义数字类型
let num1: number = 1;
num1 = 5;

// 定义布尔值
let show1: boolean = true;
show1 = false;

// 定义undefined
let unf: undefined = undefined;

// 定义null
let nl: null = null;

// 定义联合类型（既可以是这种类型，也可以是那种类型）
// 用|表示或
let a: number | string | boolean = 5;
a = "5";
a = true;

// const keys: string | null = localStorage.getItem("keys");
// const arr = keys && JSON.parse(keys);

// 定义any类型
// 任意类型
// 尽可能少用
let b: any = { x: 10 };
b = 4;
b = true;

// 定义数组类型
const arr1: number[] = [1, 2, 3, 4, 5];
const arr2: Array<string> = ["1", "2", "3"];
const arr3: (number | string)[] = [1, 2, "hello"];
const arr4: Array<string | number> = [1, 2, "hello"];
const arr5: Array<string | number> = [1, 2, "hello", str1];

// 元组类型(数组类型的扩展)
// 规定好了数组的长度以及每一项的数据类型
const arr6: [number, string, boolean] = [1, "2", true];

// 定义对象类型
// 如果用object作为对象的类型，这个对象就不能改了
const obj1: object = {
  name: "smallHua",
  age: 20,
};
// 对象的类型要用接口来定义
// 大驼峰来定义
interface Obj2Type {
  name: string;
  age: number;
  // 问号表示可选属性
  sex?: number;
  // 索引签名
  // 除了以上的属性以外，可能存在其他属性，但是不关心了
  [propName: string]: any;
}
const obj2: Obj2Type = {
  name: "smallHua",
  age: 20,
  a: 3,
  b: "sdf",
  x: true,
};
obj2.sex = 2;

interface Obj3Type {
  x: number;
  y: number;
  z: string[];
}
const obj3: Obj3Type = {
  x: 10,
  y: 20,
  z: ["a", "b", "c"],
};

interface Obj4ItemType {
  name: string;
  id: number;
}
const obj4: Obj4ItemType[] = [
  {
    name: "zhangsan",
    id: 1,
  },
  {
    name: "lisi",
    id: 2,
  },
];

interface Obj5ItemType {
  name: string;
  id: number;
  children?: Obj5ItemType[];
}
const obj5: Obj5ItemType[] = [
  {
    name: "zhangsan",
    id: 1,
  },
  {
    name: "lisi",
    id: 2,
    children: [
      {
        name: "liwu",
        id: 3,
      },
      {
        name: "liliu",
        id: 4,
      },
    ],
  },
];

interface AttrsType {
  height: number;
  weight: number;
}
interface Obj6Type {
  name: string;
  likes: string[];
  attrs: AttrsType;
}
const obj6: Obj6Type = {
  name: "zhangsan",
  likes: ["唱", "跳"],
  attrs: {
    height: 180,
    weight: 180,
  },
};

// 枚举类型（对象的扩展）
// 类似于对象，不仅可以从key找到value，还可以从value找到key
enum Color {
  Red = 10,
  Green = 15,
  Blue = 20,
}
let gn: Color = Color.Green;
console.log(gn);
let bl: string = Color[20];
console.log(bl);

// 定义函数类型
// 函数加的是返回值的类型
// 函数的参数是不会做类型推断，参数必须要手动地添加类型
function fn1(): number {
  return 10;
}
function fn2(x: number, y: string): string {
  return x + y;
}
fn2(3, "4");
function fn3(x: number, y?: number) {
  if (y) {
    return x + y;
  } else {
    return x;
  }
}
fn3(3);

// 通常情况下，函数类型是可以省略
const fn4: (x: number, y: number) => number = (
  x: number,
  y: number
): number => {
  return x + y;
};
fn4(3, 4);

// 当函数没有返回值的时候，需要加void
const fn5 = (): void => {
  console.log(123);
};

// never表示从不，没有返回值
// 死循环或者报错的时候
const fn6 = (): never => {
  while (true) {}
};
const fn7 = (): never => {
  throw new Error("报错了");
};

// 泛型类型（函数的扩展）
// const fn8 = <T>(x: T, y: T) => {
//   return 100;
// }
function fn8<T>(x: T, y: T) {
  return 100;
}
fn8<string>("3", "4");

// const ref = <T>(n: T) => {
//   return {
//     value: n
//   }
// }
// const count = ref<number>(5);

interface ObjType {
  nm: string;
}
// 定义类的类型
class Animal {
  constructor(obj: ObjType) {
    // console.log(456);
    this.title = obj.nm;
  }
  title: string = "dog";
  sayTitle(): string {
    return this.title;
  }
}
// 类的继承
class Dog extends Animal {
  // constructor是构造函数的构造器
  // constructor是在当这个类被实例化的时候调用
  constructor(obj: ObjType) {
    super(obj);
    console.log(123);
  }
}
// 类的实例化
const dog = new Dog({
  nm: "cat",
});

console.log(dog.title);
